/*
 *  OSXBGEPHY.cpp
 *  OSXBGE
 *
 *  Created by Stafford Brunk on 2/6/09.
 *  Copyright 2009 Northpole Software. All rights reserved.
 *
 */

#include "OSXBGE.h"


#define super IOEthernetController

OSDefineMetaClassAndStructors( OSXBGE, IOEthernetController );


#pragma mark -
#pragma mark Start, Stop, and Free
#pragma mark -

bool OSXBGE::initDriverObjects( IOService * provider )
{	
    // When transmit ring is full, packets are queued here
	fTransmitQueue = createOutputQueue();
	if (fTransmitQueue == 0)
	{
		DLOG ("OSXBGE::initDriverObjects - No transmit queue was created");
		return false;
	}
	
    // Get our work loop
	
    IOWorkLoop * workLoop = (IOWorkLoop *) getWorkLoop();
    if (!workLoop)
    {
        DLOG("OSXBGE::initDriverObjects - No work loop was created");
        return false;
    }
	
    // Create a mbuf cursor for transmit and receive
	
    fTxMbufCursor = IOMbufNaturalMemoryCursor::withSpecification(BGE_MAX_FRAMELEN, 1);
	fRxMbufCursor = IOMbufNaturalMemoryCursor::withSpecification(BGE_MAX_FRAMELEN, 1);
	
    if (!fTxMbufCursor || !fRxMbufCursor)
    {
        DLOG("OSXBGE::initDriverObjects - TX/RX mbuf cursor allocation error");
        return false;
    }
	
    // Attach an interrupt event source to our work loop
    fInterruptSrc = IOFilterInterruptEventSource::filterInterruptEventSource(this,
																			 &OSXBGE::interruptHandler,
																			 &OSXBGE::interruptFilter,
																			 provider);
    if (!fInterruptSrc ||
        (workLoop->addEventSource(fInterruptSrc) != kIOReturnSuccess))
    {
        DLOG("OSXBGE::initDriverObjects - IOInterruptEventSource error\n");
        return false;
    }
	
	fInterruptSrc->enable();
	
	//Watchdog timer registration
    fTimerSrc = IOTimerEventSource::timerEventSource (this, &OSXBGE::timeoutHandler);
	
	if (!fTimerSrc || (workLoop->addEventSource(fTimerSrc) != kIOReturnSuccess))
    {
        DLOG("OSXBGE::initDriverObjects - IOTimerEventSource error\n");
        return false;
    }
	
    //Dictionary to hold IONetworkMediums
	fMediaDict = OSDictionary::withCapacity(5);
	if(!fMediaDict)
	{
		DLOG("OSXBGE::initDriverObjects - Error creating medium dictionary!");
		return false;
	}
	
    return true;
}

bool OSXBGE::start( IOService *provider )
{
    bool  success = false;
    bool  started = false;

#ifdef DEBUG
	DLOG ("OSXBGE::start");
#endif
	
	UInt8 eaddr[6];
	
    do {
        if (super::start(provider) == 0)
		{
			IOLog ("OSXBGE::start - super::start failed");
            break;
		}
		
        started = true;
		
        // Allocate memory for bge driver's softc structure
		
        sc = IONew(struct bge_softc, 1);
        if (sc == 0)
        {
            DLOG("OSXBGE::start - Could not allocate bge_softc struct");
            break;
        }
        memset(sc, 0, sizeof(*sc));
		
        // Allocate transmit and receive memory
        if (allocateTxMemory() == false)
        {
            DLOG("OSXBGE::start - allocateTxMemory failed");
            break;
        }
		
        if (allocateRxMemory() == false)
        {
            DLOG("OSXBGE::start - allocateRxMemory failed");
            break;
        }
		
        // Cache provider and attempt to become its exclusive client
		
        fPCIDevice = OSDynamicCast(IOPCIDevice, provider);
        if (fPCIDevice == 0) break;
		
        fPCIDevice->retain();
		
        if (fPCIDevice->open(this) == false)
            break;
		
        // Allocate and init support objects used by the driver
		
        if (!initDriverObjects(provider))
        {
            DLOG("OSXBGE::start - Initialization of driver objects has failed");
            break;
        }
		
		//Map device regiters
		fRegMap = fPCIDevice->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0);
		
		if (!fRegMap)
		{
			DLOG("OSXBGE::start - Failed to map device registers");
			break;
		}
		
		//Map kernel virtual memory
		sc->bge_bhandle = (void *) fRegMap->getVirtualAddress();
		
		//PCI Configuration
		fPCIDevice->setBusMasterEnable(true);
		fPCIDevice->setMemoryEnable(true);
		fPCIDevice->setIOEnable(false);
		
		DLOG("Mapped from 0x%X of length %d.\n", fPCIDevice->configRead32(kIOPCIConfigBaseAddress0), fRegMap->getLength());
		DLOG("ven_id [%04x] dev_id [%04x]", fPCIDevice->configRead16(kIOPCIConfigVendorID), fPCIDevice->configRead16(kIOPCIConfigDeviceID));
		
		//Run device setup
		bge_setup(sc);
		
		//Gracefully reset chip
		bge_stop_fw(sc);
		bge_sig_pre_reset(sc, BGE_RESET_START);
		
		if (bge_reset())
		{
			DLOG("OSXBGE::start - Reset failed!");
			break;
		}
		
		bge_sig_legacy(sc, BGE_RESET_START);
		bge_sig_post_reset(sc, BGE_RESET_START);
		
		//Initialize the chipset
		bge_chipinit(sc);
		
		//Get MAC address
		bge_get_eaddr(sc, eaddr);
		
		fEnetAddr.bytes[0] = eaddr[0];
        fEnetAddr.bytes[1] = eaddr[1];
        fEnetAddr.bytes[2] = eaddr[2];
        fEnetAddr.bytes[3] = eaddr[3];
        fEnetAddr.bytes[4] = eaddr[4];
        fEnetAddr.bytes[5] = eaddr[5];
		
        DLOG("MAC address = %02x:%02x:%02x:%02x:%02x:%02x\n",
			 fEnetAddr.bytes[0], fEnetAddr.bytes[1],
			 fEnetAddr.bytes[2], fEnetAddr.bytes[3],
			 fEnetAddr.bytes[4], fEnetAddr.bytes[5]);
		
		//Get the PHY ID
		getPhyID();
#ifdef DEBUG
		DLOG("PHY ID: 0x%08x", fPhyID);
#endif
		
		//Publish media support
		publishMedia();
		
		if (publishMediumDictionary(fMediaDict) == false)
		{
			DLOG("OSXBGE::start - Could not get media support");
			break;
		}
		
        success = true;
		
    } while (0);
	
    // Close our provider, it will be re-opened on demand when driver
    // is enabled by BSD or KDP.  No hardware access is allowed below
    // this line to guarantee single threaded access to hardware.
	
    if (fPCIDevice) fPCIDevice->close(this);
	
    do {
        if (success == false) break;
        success = false;
		
        // Attach an IOEthernetInterface
        if (attachInterface((IONetworkInterface **) &fNetif, false) == false)
            break;
		
        fNetif->registerService();
        success = true;
		
    } while (0);
	
    if (started && !success)
    {
        super::stop(provider);
    }
	
    return success;
}

void OSXBGE::stop(IOService * provider)
{
#ifdef DEBUG
	DLOG("OSXBGE::stop");
#endif
	super::stop(provider);
}

bool OSXBGE::configureInterface( IONetworkInterface * netif )
{
    IONetworkData * data;
	
    if (super::configureInterface(netif) == false)
        return false;
	
    // Get the generic network statistics structure
    data = netif->getParameter(kIONetworkStatsKey);
    if (!data || !(fNetStats = (IONetworkStats *) data->getBuffer()))
    {
        DLOG("OSXBGE::configureInterface - No network statistics\n");
        return false;
    }
	
    // Get the Ethernet statistics structure
    data = netif->getParameter(kIOEthernetStatsKey);
    if (!data || !(fEtherStats = (IOEthernetStats *) data->getBuffer()))
    {
        DLOG("OSXBGE::configureInterface - No Ethernet statistics\n");
        return false;
    }
	
    return true;
}

void OSXBGE::free( void )
{
#define RELEASE(x) do { if(x) { (x)->release(); (x) = 0; } } while(0)

#ifdef DEBUG
    DLOG("OSXBGE::free");
#endif
	
	RELEASE (fNetif);
	
    if (fInterruptSrc && fWorkLoop)
    {
        fWorkLoop->removeEventSource(fInterruptSrc);
    }
	
    RELEASE( fInterruptSrc  );
	RELEASE( fTimerSrc		);
    RELEASE( fTxMbufCursor  );
	RELEASE( fRxMbufCursor  );
	RELEASE( fRegMap        );
	RELEASE( fMediaDict     );
    RELEASE( fPCIDevice     );
    RELEASE( fWorkLoop      );
	RELEASE( fTransmitQueue );
	
    releaseTxMemory();
    releaseRxMemory();
	
	if (fKDPMbuf)
	{
		freePacket(fKDPMbuf);
	    fKDPMbuf = 0;
	}
	
    if (sc)
    {
        IODelete(sc, struct bge_softc, 1);
        sc = 0;
    }
	
    return super::free();
}

#pragma mark -
#pragma mark MAC Address Functions
#pragma mark -

IOReturn OSXBGE::getHardwareAddress( IOEthernetAddress *address )
{
	address->bytes[0] = fEnetAddr.bytes[0];
	address->bytes[1] = fEnetAddr.bytes[1];
	address->bytes[2] = fEnetAddr.bytes[2];
	address->bytes[3] = fEnetAddr.bytes[3];
	address->bytes[4] = fEnetAddr.bytes[4];
	address->bytes[5] = fEnetAddr.bytes[5];
	
	return kIOReturnSuccess;
}

IOReturn OSXBGE::setHardwareAddress( const IOEthernetAddress * address )
{
	fEnetAddr.bytes[0] = address->bytes[0];
	fEnetAddr.bytes[1] = address->bytes[1];
	fEnetAddr.bytes[2] = address->bytes[2];
	fEnetAddr.bytes[3] = address->bytes[3];
	fEnetAddr.bytes[4] = address->bytes[4];
	fEnetAddr.bytes[5] = address->bytes[5];
	
	return kIOReturnSuccess;
}

void OSXBGE::initPCIConfigSpace( IOPCIDevice * pci )
{
    UInt16 cmd = pci->configRead16(kIOPCIConfigCommand);
	
    cmd |= (	kIOPCICommandBusMaster      |
				kIOPCICommandMemorySpace     |
				kIOPCICommandMemWrInvalidate );
	
    cmd &= ~kIOPCICommandIOSpace; //disable IO space
	
    pci->configWrite16(kIOPCIConfigCommand, cmd);
#ifdef DEBUG
    DLOG("OSXBGE PCI CMD = %04x\n", pci->configRead16(kIOPCIConfigCommand));
#endif
}

#pragma mark -
#pragma mark Work Loop
#pragma mark -

bool OSXBGE::createWorkLoop( void )
{
    fWorkLoop = IOWorkLoop::workLoop();
    return (fWorkLoop != 0);
}

IOWorkLoop * OSXBGE::getWorkLoop( void ) const
{
    return fWorkLoop;
}

#pragma mark -
#pragma mark Vender and Model Strings
#pragma mark -

const OSString * OSXBGE::newVendorString( void ) const
{
    UInt16 vendor = fPCIDevice->configRead16(0);
	
	switch (vendor)
	{
		case BCOM_VENDORID:
			return OSString::withCString("Broadcom");
			break;
		case ALTEON_VENDORID:
			return OSString::withCString("Alteon");
			break;
		case SK_VENDORID:
			return OSString::withCString("SysKonnect");
			break;
		case DELL_VENDORID:
			return OSString::withCString("Dell");
			break;
		case APPLE_VENDORID:
			return OSString::withCString("Apple");
			break;
		case ALTIMA_VENDORID:
			return OSString::withCString("Altima");
			break;
		case SUN_VENDORID:
			return OSString::withCString("Sun");
			break;
		default:
			return OSString::withCString("Unknown Vendor");
			break;
	}
}

/*
 *	I have only added the most common models.  We'll as more as necessary
 */
const OSString * OSXBGE::newModelString( void ) const
{
	UInt16 device = fPCIDevice->configRead16(2);
	
	switch (device)
	{
		case BCOM_DEVICEID_BCM5906M:
			return OSString::withCString("5906M");
			break;
		case BCOM_DEVICEID_BCM5788:
			return OSString::withCString("5788");
			break;
		case BCOM_DEVICEID_BCM5789:
			return OSString::withCString("5789");
			break;
		case BCOM_DEVICEID_BCM5755:
			return OSString::withCString("5755");
			break;
		case BCOM_DEVICEID_BCM5700:
			return OSString::withCString("5700");
			break;
		case BCOM_DEVICEID_BCM5701:  //also applies to APPLE_DEVICE_BCM5701
			return OSString::withCString("5701");
			break;
		default:
			return OSString::withCString("Unknown Device");
			break;
	}
}

#pragma mark -
#pragma mark Enable and Disable
#pragma mark -

IOReturn OSXBGE::enable( IONetworkInterface * netif )
{
    if (fEnabledForBSD) return kIOReturnSuccess;
	
    fEnabledForBSD = setActivationLevel(kActivationLevelBSD);
	
    return fEnabledForBSD ? kIOReturnSuccess : kIOReturnIOError;
}

IOReturn OSXBGE::disable( IONetworkInterface * netif )
{
    fEnabledForBSD = false;
	
    setActivationLevel(fEnabledForKDP ? kActivationLevelKDP : kActivationLevelNone);
	
    return kIOReturnSuccess;
}

#pragma mark -
#pragma mark Activation Methods
#pragma mark -

bool OSXBGE::increaseActivationLevel( UInt32 newLevel )
{
	bool success = false;
	
#ifdef DEBUG
	DLOG("OSXBGE - Increase activation level: %04x", newLevel);
#endif
	
	switch (newLevel)
    {
        case kActivationLevelKDP:
			
			if((fPCIDevice == 0) || (fPCIDevice->open(this) == false))
			   break;
			
			if (initTxRing() == false || initRxRing() == false)
				break;
			
			// Set current medium.
			if (selectMedium(getCurrentMedium()) != kIOReturnSuccess)
					DLOG("OSXBGE::increaseActivationLevel - %s: selectMedium error\n", getName());
	
			// Start the watchdog timer.
			fTimerSrc->setTimeoutMS(kWatchdogTimerPeriodMS);
	
			phyReportLinkStatus(true);
	
			/* Start autonegotiation */
			bge_miibus_writereg(sc, PHY_ADDR, BRGPHY_MII_BMCR, BRGPHY_BMCR_AUTOEN | BRGPHY_BMCR_STARTNEG);
			bge_miibus_writereg(sc, PHY_ADDR, BRGPHY_MII_IMR, 0xFF00);
			
			success = true;
			fActivationLevel = newLevel;
			break;
		
		case kActivationLevelBSD:
			fTransmitQueue->setCapacity(1024);
			fTransmitQueue->start();
			
			success = true;
			fActivationLevel = newLevel;
			break;
	}

	return success;
}

bool OSXBGE::decreaseActivationLevel( UInt32 newLevel )
{
	bool success = true;

#ifdef DEBUG
	DLOG("OSXBGE - Decrease activation level: %04x", newLevel);
#endif
	
	
    switch (newLevel)
    {
        case kActivationLevelKDP:
			
            bge_reset();
			
            fTimerSrc->cancelTimeout();
			
            setLinkStatus( kIONetworkLinkValid );
            IOSleep(20);
			
            freeTxRingPackets();
            freeRxRingPackets();
			
			fTransmitQueue->setCapacity(0);
            fTransmitQueue->flush();
			
            if (fPCIDevice)
                fPCIDevice->close(this);
			
            break;
			
        case kActivationLevelBSD:
			
            fInterruptEnabled = false;
			
            fTransmitQueue->stop();
			
            break;
    }
	
    return success;
}

//---------------------------------------------------------------------------

bool OSXBGE::resetCurrentActivationLevel( void )
{
    return false;
}

//---------------------------------------------------------------------------

bool OSXBGE::setActivationLevel( UInt32 inLevel )
{
    bool success = false;
	
    DLOG("setActivationLevel %ld\n", inLevel);
	
    if (fActivationLevel == inLevel) return true;
	
    for ( ; fActivationLevel > inLevel; fActivationLevel--) 
    {
        if ((success = decreaseActivationLevel(fActivationLevel)) == false)
            break;
    }
	
    for ( ; fActivationLevel < inLevel; fActivationLevel++ ) 
    {
        if ((success = increaseActivationLevel(fActivationLevel+1)) == false)
            break;
    }
	
    return success;
}


#pragma mark -
#pragma mark ••• Transmit/Receive •••
#pragma mark -

bool OSXBGE::allocateTxMemory( void )
{
	return true;
}

bool OSXBGE::allocateRxMemory( void )
{
	return true;
}

bool OSXBGE::initTxRing( void )
{
	return true;
}

bool OSXBGE::initRxRing( void )
{
	return true;
}

bool OSXBGE::updateRxDescriptor(UInt32 index)
{
	return true;
}

void OSXBGE::releaseTxMemory( void )
{
	
}

void OSXBGE::releaseRxMemory( void )
{
    
}

void OSXBGE::freeTxRingPackets( void )
{
	
}

void OSXBGE::freeRxRingPackets( void )
{
	
}

#pragma mark -
#pragma mark Output
#pragma mark -

IOOutputQueue *OSXBGE::createOutputQueue ( void )
{
#ifdef DEBUG
	DLOG("Creating output queue");
#endif
	return IOGatedOutputQueue::withTarget(this, getWorkLoop());
}

void OSXBGE::receivePacket( void * pkt_data, UInt32 * pkt_size, UInt32   timeoutMS )
{
}

UInt32 OSXBGE::outputPacket( mbuf_t packet, void * param )
{
    return 1;
}

#pragma mark -
#pragma mark Interrupt and Timer Methods
#pragma mark -

void OSXBGE::interruptHandler (OSObject * target, IOInterruptEventSource * src, int count)
{
	DLOG("Interrupt Handler");
}

bool OSXBGE::interruptFilter (OSObject * target, IOFilterInterruptEventSource * src)
{
	return true;
}

//Watchdog
void OSXBGE::timeoutOccurred (IOTimerEventSource * src)
{
	phyReportLinkStatus(false);
	fTimerSrc->setTimeout(kWatchdogTimerPeriodMS);
}

void OSXBGE::timeoutHandler (OSObject * target, IOTimerEventSource * src)
{
	((OSXBGE *) target)->timeoutOccurred(src);	
}

void OSXBGE::interruptOccurred( IOInterruptEventSource * src, int count )
{
	
}

bool OSXBGE::receiveInterruptOccurred()
{
	return true;
}

void OSXBGE::transmitInterruptOccurred()
{
	
}


#pragma mark -
#pragma mark Media
#pragma mark -

IOReturn OSXBGE::selectMedium( const IONetworkMedium * medium )
{
	bool  success;
	
	if ( OSDynamicCast(IONetworkMedium, medium) == 0 )
    {
        // Defaults to Auto.
        medium = _phyGetMediumWithType( MEDIUM_TYPE_AUTO );
        if ( medium == 0 )
		{
			DLOG("Error getting medium");
			return kIOReturnError;
		}
    }
	
	// Program PHY to select the desired medium.
	success = phySetMedium( (mediumType_t) medium->getIndex() );
	
	// Update the current medium property.
	if ( success && !setCurrentMedium(medium) )
		DLOG("%s: setCurrentMedium error\n", getName());
	
	return ( success ? kIOReturnSuccess : kIOReturnIOError );
}

IOReturn OSXBGE::setPromiscuousMode ( bool active)
{
	//ifnet_t *ifp;
//	
//	BGE_LOCK_ASSERT(sc);
//	
//	ifp = sc->bge_ifp;
//	
//	/* Enable or disable promiscuous mode as needed. */
//	if (ifp->if_flags & IFF_PROMISC)
//		BGE_SETBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
//	else
//		BGE_CLRBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
	
	return kIOReturnSuccess;
}

IOReturn OSXBGE::setMulticastMode (bool active)
{
	return kIOReturnSuccess;
}

IOReturn OSXBGE::setMulticastList ( IOEthernetAddress *mcAddrList, UInt32 mcAddrCount)
{
    return kIOReturnSuccess;
}